home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
CICA 1993 April
/
CICA MS Windows - April 1993.iso
/
unzipped
/
programr
/
bcpp
/
ddispatc
/
ddispatc.top
Wrap
Text File
|
1991-12-05
|
9KB
|
193 lines
How OWL does dynamic dispatch virtual table's (DDVT's).
The short version:
We have extended the language so that you can associate an unsigned
int with a virutal function. An example of the syntax is:
class A {
virtual void foo() = [ 12 ];
};
When the compiler encounters this syntax, it will generate the virtual
table for class A in a new way. It will associate the number 12 with
the address of the function A::foo(void). This is all the compiler does.
Within OWL, there are a few WndProc's which have been exported.
When they get a windows message, windows tells it what window this
message is for through the hWnd parameter. From this OWL figures out
which of the users object's this message is for. It then scans the
virtual table's of this class, then its base classes looking for a
function which has been associated with that message. It then
calls that function directly, passing it a RTMessage ( referenence to
a TMessage structure), which describes the message.
So most of the work for dynamic dispatching is done by OWL.
The long version:
When you go
class A {
public:
virtual void foo() = [ 12 ];
};
where 12 integer (2 byte expression), and the function foo is virtual,
foo is now called a dynamically dispatched virtual function.
To understand the format of the virtual function table, lets take
a more complicated example.
class A {
virtual void dfoo() = [2];
virtual void nfoo();
virtual void dfoo2() = [3];
};
Assuming default settings for virtual tables, (ie smart and not far)
The compiler will generate a virtual function table called @@A@
withing a virtual segment @A@ within the _DATA segment. (Virtual
segments are discuessed in the Turbo Assembler manuals).
Here is a commented version of what it will look like:
_DATA segment word public 'DATA'
@A@ segment virtual
@@A@ label byte // name of VTable for class A
dd @A@dfoo$qv // list of all dispatched functions, they are
dd @A@dfoo2$qv // always far addresses, despite memory model.
// the dispatched functions are listed in the
// order they are declared.
db 2 // following the dispatched functions are the unsigned ints
db 0 // they're dispatched to. These int's are listed in the
db 3 // order of the dispatched functions also.
db 0
// after the dispatch numbers comes a
db 2 // count of how manyu dispatched functions
db 0 // there are.
db 0 // Following the count is the address of the normal
db 0 // part of A's first base class'es virtual table. In this
// case it is null since A has no base class.
dw @A@nfoo$qv
// Finally comes the addresses of the regular virtual functions.
// This part looks like a normal VTable.
@A@ ends
_DATA ends
The attribute of being virtually dispatched is inheritted. In the
following,
class B : public A {
dfoo();
};
dfoo is virtually dispatched, since it was in A. B's Vtable would like,
@@B@ label byte
dd @B@dfoo$qv // B's dispatched functions,
db 2 // dispatch numbers
db 0
db 1 // count of how many dispatched functions it has.
db 0
dw @@A@+16 // address of where A's Vtable ends. This will be
// the start of where the regular VTable is.
dw @A@nfoo$qv
dw @A@nfoo2$qv
The pointer to the base class VTable is how Owl finds out what messages
instances of class B have inherited Dynamic Dispatch functions for.
You have to be careful about mixing multiple inheritence with dynamic
dispatching. The VDDT for a given class only stores a pointer to the
immediate base classes virtual table. So given,
class C : A, B { ... }
OWL will only be able to use dispatched functions from A and C, not B.
All other aspects of multiple inheritence will function the same.
How Owl dispatches the message:
When TWindow objects are created, there addresses are stored in an
Association whose key is hwnd's (window handles). So when StdWndProc
gets a message, (StdWndProc is the standard window procedure for
OWL objects, it processes most of the messages an OWL app receives), it
uses the hwnd parameter to look up the address, or this pointer of the
TWindow object that this message is for.
Before it scans the DDVT's for a function to call, it does a little
checking on the message number to find functions dispatches to child
ID's or menu commands. For example, if the message is WM_COMMAND,
it adds wParam ( which identifes the actual command selected ) to
CM_FIRST and then searches for that number in the DDVT's.
Searching happens in this way, given the address the TWindow object, OWL
assumes the first thing at that address will be the pointer to the VTable
pointer for that object. Normally, when the compiler lays out the
physical memory for a class, all the data for the class comes first,
then the Vtable pointer. However, devired classes must have the Vtable
pointer in the same location. So given,
class A { in memory a
int a, b; an A object b
virutal foo(); would look like: vptr
};
class B : A { in memory a
int c; a B object b
virtual foo2(); would look like: vptr
}; c
The reason why OWL can assume a vtable pointer at the start of an
object is because all OWL objects derive first from Object as their
base class. Object is an abstract base class with no data (save a
static member, which is not stored in instances of the class) but
several virtual functions. Thus the location of the Vtable pointer
for an Object and all its derived is at the start of the class
instance. Given the this pointer for an instance of a class, one
one could get the vtable pointer by these expressions:
#define offsetofVTptr( x ) sizeof( x ) - sizeof( void * )
char *VTBPtr = *( char **)( (char *)this + offsetofVTptr( classname ));
This assumes the vtable pointer is of the default size for the
memory model, and that classname is first class in a single
inheritence hierarchy having virtual functions, and that what
this points to is dervived from that class.
Actually, the VTable pointer does not point at the start of
the VTable, but the start of the normal part of the VTable.
In our example above, this would be @@A@+16. After Owl has the address
of the normal part of the vtable, it passes this, and the message its
searching for, to the assembly function Ddispatch to return the
address of the function a message is dispatched to. The way Ddispatch
scans, is that it reads backwards from the vtable address given,
skiping the address for the base class vtable for now, and reads
the count of the number of dispatched functions, and scans backwards
that many words in the VTable looking to see if this object has a
function dispacted to that message. If so it returns the address
of that function, if not it repeats this for the base class. If
there is no function dispatched to this message, it returns null.
OWL then proceeds directly to doing default message processing for
the message. Once Owl has an address for a dispatches function, it
calls it directly after pushing the this pointer and an RTMessage on
the stack. (so no matter what you declare a dispatched function to
take, in OWL, it allows gets an RTMessage ).
You cannot call a dispatched function directly, the virtual function
mechanism is not supported for them. The code needed to support the
virtual function mechanism given the vtable format would be very
complicated. Since the vtable pointer points immediatly past
the dispatched functions, the compiler cannot index off it like it
normally does. Also the addresses of dispatched functions from the base
which are not overidden in the derived class are not stored in the
derived class'es vtable ).
There is a case where the compiler will generate a call to a dispatched
function, here is an example of that:
class A { public: virtual foo() = [ 2 ]; };
A aa;
main() { aa.foo() }
Here, the function is defined in class A, and is not being called through
a reference, so the compiler knows exactly which object is
invoking it. In this case the compiler calls foo like it would a non
virtual function, by pushing the address of aa on the stack and calling
foo directly.